package server;
import java.io.File;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;

import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocket;

import sameClasses.MethodNotFoundException;
import sameClasses.NetworkPacket;
import sameClasses.PluginNotExistingException;
import sameClasses.User;
import sameClasses.UserNotExistingException;

public class Server {

  private PluginManager myPlugins;
  private ServerOrb myServerOrb;

  public Server(User admin, File datafile, char[] masterpw, int port, String keystorefile, String keystorepw) {
    this.myPlugins = new PluginManager(admin, masterpw, datafile);
    myServerOrb = new ServerOrb(myPlugins, port, keystorefile, keystorepw);
    myServerOrb.start();
  }
  
  public PluginManager getPluginManager() {
    return this.myPlugins;
  }
  
  public void exitServer() {
    myServerOrb.exitServerOrb();
    this.myPlugins.stopMe();
  }
}

class ServerOrb extends Thread {

  PluginManager pm;
  static int ServerPort;
  boolean exit = false;
  private String keystorefile;
  private String keystorepw;
  SSLServerSocketFactory factory;
  SSLServerSocket serversocket;
  SSLSocket client;
  boolean serverOrbListening;
  
  public ServerOrb(PluginManager pm, int port, String keystorefile, String keystorepw) {
    this.pm = pm;
    ServerPort = port;
    this.keystorefile = keystorefile;
    this.keystorepw = keystorepw;
  }
  
  public void run() {
	  try {
			//System.setProperty("javax.net.debug","all"); // TLS Debugging
	        System.setProperty("javax.net.ssl.keyStore",this.keystorefile); // Keystore File in den Umgebungsvariablen festlegen
	        System.setProperty("javax.net.ssl.keyStorePassword", this.keystorepw); // Keystore Passwort in den Umgebungsvariablen festlegen
	        factory = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault(); // Erzeugt eine SSLServrSocketFactory mit Standarteinstellungen
	        serversocket = (SSLServerSocket) factory.createServerSocket(ServerPort); // ServerSocket erzeugen und an den Port binden
	        //serversocket.setEnabledCipherSuites(new String[] {"TLS_RSA_WITH_AES_256_CBC_SHA"}); // nur TLS mit AES 256 CBC SHA zulassen => FUTURE USE
	        this.serverOrbListening = true;
	        System.out.println("[ServerOrb] ServerSocket listening on Port " + ServerPort);
	        while (this.serverOrbListening) {
	          try {
	            client = (SSLSocket) serversocket.accept();
	            new WorkerThread(pm, client).start();
	          } catch(java.net.SocketException e) {
	            //System.out.println(".");
	          }
	        }
	      } catch (java.io.IOException e) {
	        e.printStackTrace();
	      }
  }

  public void exitServerOrb() {
    if(serversocket.isBound()) {
      try {
        serversocket.setReuseAddress(true);
        serversocket.close();
        this.serverOrbListening = false;
      } catch(IOException e) {
        e.printStackTrace();
      }
    }
  }
}

class WorkerThread extends Thread {

  PluginManager pm = null;
  Socket client = null;
  //public static final int BUFFER_SIZE = 100;

  public WorkerThread(PluginManager pm, SSLSocket client) {
    this.pm = pm;
    this.client = client;
  }

  @Override
  public void run() {
    System.out.println(this.getId());
    NetworkPacket networkdata;
    try {
      ObjectInputStream in = new ObjectInputStream(client.getInputStream());
      ObjectOutputStream out = new ObjectOutputStream(client.getOutputStream());
      networkdata = (NetworkPacket) in.readObject();
      //System.out.println(networkdata);
      synchronized (pm) {
        //System.out.println("working on incoming data");
        out.writeObject(pm.commToPlugin(networkdata));
        //System.out.println("work done - output object written to OutputStream!");
        out.flush();
        out.close();
        in.close();
      }
    } catch (ClassNotFoundException e) {
      e.printStackTrace();
    } catch (java.io.EOFException e) {
      //e.printStackTrace();
    } catch (java.io.IOException e) {
      e.printStackTrace();
    } catch (PluginNotExistingException e) {
      e.printStackTrace();
    } catch (MethodNotFoundException e) {
      e.printStackTrace();
    } catch (UserNotExistingException e) {
      e.printStackTrace();
    }
  }
}